/*
 *  pack.c
 *  ratmac
 *
 *  Created by Torsten Kammer on 17.02.08.
 *  Copyright 2008 __MyCompanyName__. All rights reserved.
 *
 */

#include "pack.h"

#include <Foundation/Foundation.h>

/*
 * For some reason, the original rat guys thought it would be funny if they didn't document
 * what they found out about the RFA file format. Here's what I was able to guess looking at
 * the code they wrote.
 *
 * All fields are 32-bit unsigned integers, little endian or ASCII string.
 *
 * In versions prior to Battlefield 1.2, i.e. the stone ages, the file started with the string
 * "Refractor2 FlatArchive 1.1  ". It no longer does that, but the rest of the format seems
 * to be exactly the same.
 * Next is the offset to the record table (see below).
 * Then is a field that's either 0 or 1. I believe that zero indicates that the files are not
 * compressed, at least uncompressed files work with 0 just fine.
 * Next is an amount of magic data (see the code for the actual bytes). No idea what it does,
 * it just sits there, and I just copied it from actual RFA files. The ones shipping with BF42
 * seem to have different data than the ones in third-party mods like Interstate. No idea what
 * it all means, call me if you find out, but hey, it works, right?
 * What follows next are the actual files, compressed or not depending on the settings in the
 * record data. No idea if there is any padding between them. There is no meta-data, just the
 * very plain files (or the compressed files if compression is used. I don't write compressed
 * files myself, to be honest).
 * Finally, we have the record data. This gives the basic attributes of the files included in
 * the RFA package. It starts with a field that gives the amount of files in the package. Then,
 * we have the individual records.
 * Each of them first has a field giving the length of the file name. Then there is the actual
 * file name (ASCII, not zero-terminated, so add that 0 yourself if you need it).
 * Next is the size in bytes for the uncompressed file, followed by the size in bytes for the
 * compressed version. If both are the same, the file is not compressed. Then follows the offset
 * to the actual file data (right from the beginning of the file, no need to add or subtract
 * anything). Afterwards, there are 12 unused bytes. In real RFA files they always seem to contain
 * the bytes E0 21 13 00 00 00 00 00 00 00 00 00. Then, the next record follows directly. The very
 * last record appears to have a tail, a field of four 0 bytes.
 */
 
/*
 * Notice to anyone trying to port this to Linux or, for some arcane reason, Windows: Go screw.
 * Using Apple's APIs is making my life significantly easier. It may make your live more difficult,
 * but I don't get paid for that.
 */

@interface NSMutableData (WriteU32)

- (void)appendUnsignedLittleInt:(unsigned int)data;

@end

@implementation NSMutableData (WriteU32)

- (void)appendUnsignedLittleInt:(unsigned int)data
{
	unsigned int swapped = NSSwapHostIntToLittle(data);
	[self appendBytes:&swapped length:4];
}

@end

NSDictionary *AppendFileToRFA(NSString *fsFilename, NSString *rfaFilename, NSMutableData *rfaFile)
{
	NSData *fileData = [NSData dataWithContentsOfFile:fsFilename];
	NSDictionary *fileRecord = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInteger:[rfaFile length]], @"offset", [NSNumber numberWithUnsignedInteger:[fileData length]], @"size", rfaFilename, @"name", nil];
	[rfaFile appendData:fileData];
	return fileRecord;
}

NSArray *SearchDirectory(NSString *directoryName, NSString *resultDirName, NSMutableData *rfaFile, NSArray *skipPatterns)
{
	NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryName error:nil];
	NSMutableArray *result = [NSMutableArray array];
	for (NSString *filename in files)
	{
		// Does it match?
		BOOL shouldSkip = NO;
		for (NSString *pattern in skipPatterns)
			if ([filename isLike:pattern]) shouldSkip = YES;
		if (shouldSkip) continue;
		
		BOOL isDirectory;
		NSString *fsFilename = [directoryName stringByAppendingPathComponent:filename];
		NSString *rfaFilename = [directoryName stringByAppendingPathComponent:filename];
		
		[[NSFileManager defaultManager] fileExistsAtPath:fsFilename isDirectory:&isDirectory];
		
		if (isDirectory)
			[result addObjectsFromArray:SearchDirectory(fsFilename, rfaFilename, rfaFile, skipPatterns)];
		else
			[result addObject:AppendFileToRFA(fsFilename, rfaFilename, rfaFile)];
	}
	return result;
}

int pack(const char *root, const char *rfafile, const char **patternsToSkip)
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
	NSMutableData *rfaData = [[NSMutableData alloc] init];
	NSString *rootFolder = [NSString stringWithUTF8String:root];

	NSMutableArray *skipPatterns = [[NSMutableArray alloc] init];
	while (patternsToSkip != NULL && *patternsToSkip != NULL)
	{
		NSString *skipPattern = [NSString stringWithUTF8String:*patternsToSkip];
		[skipPatterns addObject:skipPattern];
		*patternsToSkip++;
	}

	// Assuming 1.2 or later version here, so we do not write the RFA version string
	[rfaData appendUnsignedLittleInt:0]; // Will get filled in later.
	[rfaData appendUnsignedLittleInt:0]; // Every file seems to have that.
	
	const char magicHeader[] = { 0x63, 0xEC, 0x95, 0xBF, 0xFE, 0x7B, 0x09, 0x3C, 0x3A, 0xF0, 0x72, 0xEE, 0xA4, 0x72, 0xE7, 0xD9, 0x3F, 0xCC, 0x99, 0xC0,
0xD3, 0x71, 0xC1, 0x46, 0x89, 0xBD, 0xD7, 0x53, 0xB5, 0x7E, 0x05, 0xB9, 0xF3, 0xB3, 0xDB, 0x18, 0x75, 0x94, 0x44, 0xFF,
0x9B, 0xD2, 0xB9, 0x53, 0xC4, 0x1F, 0xB4, 0xF5, 0x65, 0xF1, 0x68, 0x9F, 0x58, 0x83, 0xAF, 0x0F, 0x76, 0x1D, 0x44, 0x68,
0x67, 0x96, 0x32, 0xD5, 0xB9, 0x17, 0x8C, 0x6F, 0x30, 0x21, 0x5F, 0x61, 0x5D, 0xD2, 0xE5, 0x49, 0x72, 0x64, 0xFB, 0xE2,
0x55, 0xF6, 0xD5, 0xE1, 0xF3, 0x8F, 0xF1, 0x1C, 0xD7, 0x60, 0x49, 0xF1, 0xFB, 0x49, 0xCD, 0xE6, 0xDE, 0x9F, 0x10, 0x8D,
0xD6, 0x2D, 0x42, 0xAB, 0xA8, 0x78, 0x5E, 0x98, 0x56, 0x48, 0xA4, 0xE9, 0x38, 0x63, 0x4A, 0x4D, 0x4E, 0x9C, 0x6F, 0xB5,
0xD5, 0x0C, 0x50, 0xB8, 0x18, 0xA0, 0xBE, 0x35, 0x89, 0xD4, 0xD0, 0x3A, 0x10, 0xBD, 0xD5, 0x24, 0xA3, 0x4D, 0x8C, 0x08,
0x17, 0xD3, 0x98, 0x00, 0x4B, 0xD0, 0x12, 0x48 };
	[rfaData appendBytes:magicHeader length:sizeof(magicHeader)];
	
	NSArray *records = SearchDirectory(rootFolder, [rootFolder lastPathComponent], rfaData, skipPatterns);
	[skipPatterns release];
	
	unsigned int recordOffset = [rfaData length];
	recordOffset = NSSwapHostIntToLittle(recordOffset);
	[rfaData replaceBytesInRange:NSMakeRange(0, 4) withBytes:&recordOffset length:4];
	
	[rfaData appendUnsignedLittleInt:(unsigned int) [records count]];
	
	for (NSDictionary *record in records)
	{
		[rfaData appendUnsignedLittleInt:[[record objectForKey:@"name"] length]];
		[rfaData appendData:[[record objectForKey:@"name"] dataUsingEncoding:NSUTF8StringEncoding]];
		[rfaData appendUnsignedLittleInt:[[record objectForKey:@"size"] unsignedIntValue]]; // Uncompressed
		[rfaData appendUnsignedLittleInt:[[record objectForKey:@"size"] unsignedIntValue]]; // Compressed
		[rfaData appendUnsignedLittleInt:[[record objectForKey:@"offset"] unsignedIntValue]];
		char magicData[] = {0xE0, 0x21, 0x13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
		[rfaData appendBytes:magicData length:12];
	}
	// Tail
	[rfaData appendUnsignedLittleInt:0];
	
	NSString *rfaFilename = [NSString stringWithUTF8String:rfafile];
	[rfaData writeToFile:rfaFilename atomically:YES];
	[rfaData release];
	
	[pool release];
	
	return 0;
}